﻿#region References
using System;
using System.Text;
using System.Collections.Generic;

using Xen;
using Xen.Camera;
using Xen.Graphics;
using Xen.Ex.Graphics;
using Xen.Ex.Graphics2D;
using Xen.Ex.Material;
using Xen.Graphics.State;
using Xen.Ex.Graphics.Content;
using Xen.Ex.Geometry;
using Xen.Ex;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
#endregion

namespace Ness.Engine.Entities
{
    /// <summary>
    /// Base class for ANY drawable object
    /// </summary>
    class Actor : IDraw, IContentOwner
    {
        #region Private variables
        private MaterialShader material;
        private ContentRegister content;
        private ContentManager manager;
        #endregion

        #region Public variables
        //Model and transform data
        public ModelInstance model;
        public Matrix worldMatrix;
        public string model_path;
        public ReadOnlyArrayCollection<Transform> latestTransforms;

        //Animation and game instances
        public AnimationController animationController;
        public AnimationInstance animation;
        public Engine game;

        //Insignificant debug variables
        public List<IDraw> boundingBoxes;
        public bool hasTransformed, drawCollision;
        public bool applyLights;
        public ReadOnlyArrayCollection<Transform> boneAnimationTransforms;
        #endregion

        /// <summary>
        /// Initialises the Actor
        /// </summary>
        /// <param name="game">The engine instance currently in use</param>
        /// <param name="model">The path of the model, relative to Content</param>
        public Actor(Engine game, string model, bool applyLights)
        {
            //Set needed variables
            this.game = game;
            this.model_path = model;
            this.drawCollision = false;
            this.applyLights = applyLights;

            //Setup the material lighting
            if (applyLights)
            {
                material = new MaterialShader(game.Lighting.GetList());
                material.UsePerPixelSpecular = false;
                material.TextureMapSampler = TextureSamplerState.AnisotropicHighFiltering;
                material.NormalMapSampler = TextureSamplerState.AnisotropicLowFiltering;
            }

            //Add the actor to the content register
            game.Content.Add(this);
        }

        /// <summary>
        /// Sets the Actor's model
        /// </summary>
        /// <param name="path">The path of the model, relative to Content</param>
        public void SetModel(string path)
        {
            if (path != null && path != "")
            {
                this.model_path = path;

                //Make the new instance
                model = new ModelInstance();
                model.LightCollection = game.Lighting.GetList();
                model.ModelData = manager.Load<Xen.Ex.Graphics.Content.ModelData>(model_path);

                //This method creates a Cube for each bone in the mesh
                BuildBoundingBoxGeometry();
            }
        }

        private void BuildBoundingBoxGeometry()
        {
            boundingBoxes = new List<IDraw>();

            foreach (Xen.Ex.Graphics.Content.MeshData meshData in model.ModelData.Meshes)
            {
                foreach (Xen.Ex.Graphics.Content.GeometryData geometry in meshData.Geometry)
                {
                    for (int geometryBone = 0; geometryBone < geometry.BoneIndices.Length; geometryBone++)
                    {
                        //index of the bone (a peice of geometry may not use all the bones in the model)
                        int boneIndex = geometry.BoneIndices[geometryBone];

                        //Console.WriteLine(model.ModelData.Skeleton.BoneData[boneIndex].Name);

                        //the bounds of the geometry for the given bone...
                        Xen.Ex.Graphics.Content.GeometryBounds bounds = geometry.BoneLocalBounds[geometryBone];

                        //create the cube
                        boundingBoxes.Add(new Xen.Ex.Geometry.Cube(bounds.Minimum, bounds.Maximum));
                    }
                }
            }
        }

        private void DrawBoundingBoxes(DrawState state)
        {
            boneAnimationTransforms = new ReadOnlyArrayCollection<Transform>();

            if (model.ModelData.Animations.Length > 0)
            {
                //First, get the animated bone transforms of the model.
                //These transforms are in 'bone-space', not in world space.
                boneAnimationTransforms = model.GetAnimationController().GetTransformedBones(state);
            }

            latestTransforms = boneAnimationTransforms;
            hasTransformed = drawCollision = true;
            
            if (drawCollision)
            {
                //Get a simple shader from Xen.Ex that fills a solid colour
                Xen.Ex.Shaders.FillSolidColour shader = state.GetShader<Xen.Ex.Shaders.FillSolidColour>();

                shader.FillColour = Color.White.ToVector4();

                shader.Bind(state);

                //push the render state...
                state.PushRenderState();

                //disable back face culling
                state.RenderState.DepthColourCull.CullMode = CullMode.None;
                //set to wireframe
                state.RenderState.DepthColourCull.FillMode = FillMode.WireFrame;

                //loop through all the geometry data in the model..
                //(note, the sample model has only 1 geometry instance)


                Xen.Ex.Graphics.Content.SkeletonData modelSkeleton = model.ModelData.Skeleton;
                Matrix matrix;
                int boxIndex = 0;

                foreach (Xen.Ex.Graphics.Content.MeshData meshData in model.ModelData.Meshes)
                {
                    foreach (Xen.Ex.Graphics.Content.GeometryData geometry in meshData.Geometry)
                    {
                        //now loop through all bones used by this geometry

                        for (int geometryBone = 0; geometryBone < geometry.BoneIndices.Length; geometryBone++)
                        {
                            //index of the bone (a piece of geometry may not use all the bones in the model)
                            int boneIndex = geometry.BoneIndices[geometryBone];

                            //get the base transform of the bone (the transform when not animated)
                            Transform boneTransform = modelSkeleton.BoneWorldTransforms[boneIndex];

                            //multiply the transform with the animation bone-local transform

                            //it would be better to use Transform.Multiply() here to save data copying on the xbox
                            if (boneAnimationTransforms.Length > 0) boneTransform *= boneAnimationTransforms[boneIndex];

                            //Get the transform as a matrix
                            boneTransform.GetMatrix(out matrix);

                            matrix *= this.worldMatrix;

                            //push the matrix
                            state.PushWorldMatrix(ref matrix);

                            //draw the box
                            if (boundingBoxes[boxIndex].CullTest(state))
                                boundingBoxes[boxIndex].Draw(state);

                            boxIndex++;

                            //pop the world matrix
                            state.PopWorldMatrix();
                        }
                    }
                }

                //pop the render state
                state.PopRenderState();
            }
        }

        /// <summary>
        /// Moves the model to a Vector3 position
        /// </summary>
        /// <param name="position">The 3D Vector in which to move the model to</param>
        public void MoveTo(Vector3 position)
        {
            Matrix.CreateTranslation(ref position, out this.worldMatrix);
        }

        /// <summary>
        /// Draws the model to the render target
        /// </summary>
        /// <param name="state">Passed automatically. Ignore.</param>
        public void Draw(DrawState state)
        {
            if (model != null)
            {
                state.PushWorldMatrix(ref worldMatrix);

                //Bind the material shader to the state
                if (applyLights) material.Bind(state);

                //Draw the model
                model.Draw(state);

                state.PopWorldMatrix();

              //  DrawBoundingBoxes(state);
            }
        }

        /// <summary>
        /// Loads the content and sets the variables for re-use
        /// </summary>
        /// <param name="content">Passed automatically. Ignore.</param>
        /// <param name="state">Passed automatically. Ignore.</param>
        /// <param name="manager">Passed automatically. Ignore.</param>
        public void LoadContent(ContentRegister content, DrawState state, ContentManager manager)
        {
            this.content = content;
            this.manager = manager;

            SetModel(model_path);
        }

        public void UnloadContent(ContentRegister content, DrawState state)
        {
        }

        public bool CullTest(ICuller cull)
        {
            return true;
        }


    }
}
